<?php

/**
 * This file is part of the Framework03 PHP framework.
 * @author Walter Tamboer
 * @package Framework03
 */

/**
 * The standard controller dispatcher.
 */
class FW_Controller_Dispatcher_Standard extends FW_Controller_Dispatcher_Abstract
{
    /**
     * @var string The name of the default module to use.
     */
    private $defaultModule = 'default';
    
    /**
     * @var string The name of the default controller.
     */
    private $defaultController = 'index';

    /**
     * @var array The array with all the paths of where controllers can be found.
     */
    private $controllersDirectories = array();
    
    /**
     * @var array A list with routers used to route requests.
     */
    private $routers = array();

    /**
     * Sets the name of the default controller.
     * @param string $name The name of the default controller.
     * @return FW_Controller_Dispatcher_Standard
     */
    public function setDefaultController($name)
    {
        $this->defaultController = $name;
        return $this;
    }

    /**
     * Adds a controller router to this front controller so that requests get routed correctly.
     * @param string $name The name of the router.
     * @param FW_Controller_Router_Abstract $router The router that should be added.
     * @return FW_Controller_Front
     */
    public function addRouter($name, FW_Controller_Router_Abstract $router)
    {
        if (isset($this->routers[$name]))
            throw new FW_Controller_Router_Exception("The controller router '$name' has already been registered.", 500);

        $this->routers[$name] = $router;
        return $this;
    }

    /**
     * Dispatches the given request to the proper action controller and returns the response.
     * @param FW_Controller_Request_Abstract $request
     * @param FW_Controller_Response_Abstract $response
     * @return FW_Controller_Response_Abstract
     */
    public function dispatch(FW_Controller_Request_Abstract $request, FW_Controller_Response_Abstract $response)
    {
        foreach ($this->routers as $router)
        {
            if ($router->route($request))
                break;
        }

        $request->setDefaultController($this->defaultController);

        $controllerName = $request->getControllerName();

        if ($this->isDispatchable($request))
        {
            // Include the controller's file.
            $path = $this->getClassFilePath($controllerName);
            require_once $path;

            // Create the controller.
            $className = $controllerName;
            $controller = new $className($this);
            
            if (!($controller instanceof FW_Controller_Action))
                throw new FW_Controller_Exception('Invalid controller specified (' . $controllerName . ') because it isn\'t an instance of FW_Controller_Action.');

            // Get the action and dispatch the action to the controller.
            $action = $request->getActionName($request);

            // Get the arguments that were passed to this url.
            $arguments = $request->getArguments();

            // Do the actual dispatching.
            $controller->dispatch($action, $arguments);
        }
        else
            throw new FW_Controller_Dispatcher_Exception('Invalid controller specified (' . $controllerName . ')');

        return $response;
    }

    /**
     * Checks if the given request is dispatchable.
     * @param FW_Controller_Request_Abstract $request
     * @return boolean Returns true when the request is dispatchable; false otherwise.
     */
    public function isDispatchable(FW_Controller_Request_Abstract $request)
    {
        $className = $request->getControllerName();

        if (!$className)
            return false;

        if (class_exists($className, false))
            return true;

        $path = $this->getClassFilePath($className);

        return FW_Loader::isReadable($path);
    }

    /**
     * Gets the path to where the class file can be found.
     * @param string $className The name of the class to search for.
     * @param string $module The name of the module to search in.
     * @return string Returns the path to the class.
     */
    public function getClassFilePath($className, $module = null)
    {
        $file = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

        $path = $this->getControllerDirectory($module) . DIRECTORY_SEPARATOR . $file;

        return $path;
    }

    /**
     * Adds a path to the directory list of where controllers can be found.
     * @param string $path The path to the directory of where controllers are stored.
     * @param string $module The name of the module for the controllers.
     * @return FW_Controller_Dispatcher_Abstract
     */
    public function addControllerDirectory($path, $module = null)
    {
        if ($module == null)
            $module = $this->defaultModule;

        $path = rtrim((string)$path, '/\\');

        $this->controllersDirectories[$module] = $path;

        return $this;
    }

    /**
     * Sets the path(s) to the directory list of where controllers can be found.
     * @param mixed $path An array or string with the path to the directory of where controllers are stored.
     * @param string $module The name of the module for the controllers.
     * @return FW_Controller_Dispatcher_Abstract
     */
    public function setControllerDirectory($path, $module = null)
    {
        $this->controllersDirectories = array();
        
        if (is_array($path))
        {
            foreach ((array)$path as $module => $dir)
                $this->addControllerDirectory($dir, $module);
        }
        else if (is_string($path))
            $this->addControllerDirectory($path, $module);
        else
            throw new FW_Controller_Dispatcher_Exception('Controller directory spec must be either a string or an array');

        return $this;
    }
    
    /**
     * Gets the directory of where the controllers can be found.
     * @param string $module The name of the module to search in.
     * @return string Returns the path to the controllers directory.
     */
    public function getControllerDirectory($module = null)
    {
        if ($module == null)
            $module = $this->defaultModule;

        return $this->controllersDirectories[$module];
    }
}
